function CmapDecoder(ui)
{
    this.concepts = [];
    this.relations = [];
    this.edges = [];
    this.resources = []
    this.mxcells = [];
    this.ui = ui;
};

CmapDecoder.prototype.getNodes = function() {
    let cell,node,edge,concepts=[],rels=[],edges=[],resources=[],graph = this.ui.editor.graph;
    let getNode = (cell)=>{
        let node = {}
        node.id = cell.id
        node.value = cell.value
        node.style = cell.style
        node.x = cell.geometry.x + cell.geometry.width/2
        node.y = cell.geometry.y + cell.geometry.height/2
        node.width = cell.geometry.width
        node.height = cell.geometry.height
        return node;
    }
    let getEdge = (cell)=>{
        let edge = {}
        edge.id = cell.id
        edge.source = cell.source&&cell.source.id
        edge.target = cell.target&&cell.target.id
        if(cell.geometry&&cell.geometry.points){
            edge.geoPoints = cell.geometry&&cell.geometry.points
        }
        edge.style = cell.style
        return edge;
    }
    for (let key in graph.model.cells) {
        cell = graph.model.cells[key];
        if(cell.CustomizedType===graph.customizedNodeProps.type){
            node = getNode(cell)
            if(cell.attachments){
                resources = resources.concat(JSON.parse(cell.attachments))
            }
            if(cell.links){
                resources = resources.concat(JSON.parse(cell.links))
            }
            concepts.push(node)
        }else if(cell.CustomizedType===graph.customizedEdgeProps.type){
            node = getNode(cell)
            rels.push(node)
        }else if(cell.edge){
            edge = getEdge(cell)
            edges.push(edge)
        }
    }
    return {concepts,rels,edges,resources}
}

CmapDecoder.prototype.getDirectChildNamedElements = function (parent, name) {
    var result = ([]);
    for (var child = parent.firstChild; child != null; child = child.nextSibling) {
        if ((child != null && (child.nodeType == 1)) && (name == child.nodeName)) {
            result.push(child)
        }
    }
    return result;
};


CmapDecoder.prototype.initialize = function (graphmlElement)
{
    let cmapType = (type)=>{
        const mapping = {'cmap':'url','text-and-image':'url'}
        return mapping[type]||type
    }
    let concepts=[],concepts_geo=[],link_concepts=[],link_concepts_geo=[];
    let concept_list_ele = this.getDirectChildNamedElements(graphmlElement, 'concept-list');
    if(concept_list_ele&&concept_list_ele.length){
        let concept_ele = this.getDirectChildNamedElements(concept_list_ele[0], 'concept'),concept;
        if(concept_ele&&concept_ele.length){
            for (let i = 0; i < concept_ele.length; i++)
            {
                concept = {}
                concept.label = decodeURIComponent(concept_ele[i].getAttribute('label'))
                concept.style = concept_ele[i].getAttribute('style')
                concept.id = concept_ele[i].getAttribute('id')
                concept.type = 'Concept'
                concepts.push(concept);
            }
        }
    }

    let linking_phrase_list_ele = this.getDirectChildNamedElements(graphmlElement, 'linking-phrase-list');
    if(linking_phrase_list_ele&&linking_phrase_list_ele.length){
        let linking_phrase_ele = this.getDirectChildNamedElements(linking_phrase_list_ele[0], 'linking-phrase'),link_concept;
        if(linking_phrase_ele&&linking_phrase_ele.length){
            for (var i = 0; i < linking_phrase_ele.length; i++)
            {
                link_concept = {}
                link_concept.label = decodeURIComponent(linking_phrase_ele[i].getAttribute('label'))
                link_concept.style = linking_phrase_ele[i].getAttribute('style')
                link_concept.id = linking_phrase_ele[i].getAttribute('id')
                link_concept.type = 'Link'
                link_concepts.push(link_concept);
            }
        }
    }

    let concept_appearance_list_ele = this.getDirectChildNamedElements(graphmlElement, 'concept-appearance-list');
    if(concept_appearance_list_ele&&concept_appearance_list_ele.length){
        let concept_appearance_ele = this.getDirectChildNamedElements(concept_appearance_list_ele[0], 'concept-appearance'),concept_geo;
        if(concept_appearance_ele&&concept_appearance_ele.length){
            for (var i = 0; i < concept_appearance_ele.length; i++)
            {
                concept_geo = {}
                concept_geo.id = concept_appearance_ele[i].getAttribute('id')
                concept_geo.x = parseInt(concept_appearance_ele[i].getAttribute('x'))
                concept_geo.y = parseInt(concept_appearance_ele[i].getAttribute('y'))
                concept_geo.width = parseInt(concept_appearance_ele[i].getAttribute('width'))
                concept_geo.height = parseInt(concept_appearance_ele[i].getAttribute('height'))
                concepts_geo.push(concept_geo);
            }
        }
    }

    let linking_phrase_appearance_list_ele = this.getDirectChildNamedElements(graphmlElement, 'linking-phrase-appearance-list');
    if(linking_phrase_appearance_list_ele&&linking_phrase_appearance_list_ele.length){
        let linking_phrase_appearance_ele = this.getDirectChildNamedElements(linking_phrase_appearance_list_ele[0], 'linking-phrase-appearance'),link_concept_geo;
        if(linking_phrase_appearance_ele&&linking_phrase_appearance_ele.length){
            for (var i = 0; i < linking_phrase_appearance_ele.length; i++)
            {
                link_concept_geo = {}
                link_concept_geo.id = linking_phrase_appearance_ele[i].getAttribute('id')
                link_concept_geo.x = parseInt(linking_phrase_appearance_ele[i].getAttribute('x'))
                link_concept_geo.y = parseInt(linking_phrase_appearance_ele[i].getAttribute('y'))
                link_concept_geo.width = parseInt(linking_phrase_appearance_ele[i].getAttribute('width'))
                link_concept_geo.height = parseInt(linking_phrase_appearance_ele[i].getAttribute('height'))
                link_concepts_geo.push(link_concept_geo);
            }
        }
    }

    let connection_list_ele = this.getDirectChildNamedElements(graphmlElement, 'connection-list');
    if(connection_list_ele&&connection_list_ele.length){
        let connection_ele = this.getDirectChildNamedElements(connection_list_ele[0], 'connection'),connection;
        if(connection_ele&&connection_ele.length){
            for (var i = 0; i < connection_ele.length; i++)
            {
                connection = {}
                connection.id = connection_ele[i].getAttribute('id')
                connection.from = connection_ele[i].getAttribute('from-id')
                connection.to = connection_ele[i].getAttribute('to-id')
                if(connection_ele[i].getAttribute('geo-points')){
                    connection.geoPoints = JSON.parse(decodeURIComponent(connection_ele[i].getAttribute('geo-points')))
                }
                connection.style = connection_ele[i].getAttribute('style')
                this.edges.push(connection);
            }
        }
    }

    concepts.map((concept)=>{
        let concept_geo = concepts_geo.find((concept_geo)=>{
            return concept_geo.id === concept.id
        })
        return Object.assign(concept,concept_geo)
    })
    this.concepts = concepts;

    link_concepts.map((link_concept)=>{
        let link_concept_geo = link_concepts_geo.find((link_concept_geo)=>{
            return link_concept_geo.id === link_concept.id
        })
        return Object.assign(link_concept,link_concept_geo)
    })
    this.relations = link_concepts;

    let resource_group_list_ele = this.getDirectChildNamedElements(graphmlElement, 'resource-group-list');
    let resource_groups =[],resources = []
    if(resource_group_list_ele&&resource_group_list_ele.length){
        let resource_group_ele = this.getDirectChildNamedElements(resource_group_list_ele[0], 'resource-group'),resource_group;
        if(resource_group_ele&&resource_group_ele.length){
            for (let i = 0; i < resource_group_ele.length; i++)
            {
                resource_group = {}
                resource_group.pid = resource_group_ele[i].getAttribute('parent-id')
                resource_group.type = resource_group_ele[i].getAttribute('group-type')
                let resource_ele = this.getDirectChildNamedElements(resource_group_ele[i], 'resource'),resource
                if(resource_ele&&resource_ele.length) {
                    resource_group.resources = []
                    for (let j = 0; j <resource_ele.length;j++){
                        resource = {}
                        resource.id = resource_ele[j].getAttribute('id')
                        resource.name = resource_ele[j].getAttribute('label')
                        resource.url = resource_ele[j].getAttribute('resource-url')
                        resource.pid = resource_group.pid
                        resource.type = cmapType(resource_group.type)
                        resource_group.resources.push(resource)
                        resources.push(resource)
                    }
                }
                resource_groups.push(resource_group)
            }
        }
    }
    this.resources = resources;

    this.concepts.map((concept)=>{
        let find_resource = this.resources.filter((resource)=>{
            return resource.pid === concept.id
        })
        if(find_resource&&find_resource.length){
            concept.links = [],concept.attachments = []
            for(let resource of find_resource){
                if(resource.type === 'url'){
                    concept.links.push(resource)
                }else{
                    concept.attachments.push(resource)
                }
            }
        }
        return concept
    })
};

CmapDecoder.prototype.processPage = function (graph)
{
    var codec = new mxCodec();
    var node = codec.encode(graph.getModel());
    node.setAttribute("style", "default-style2");
    var modelString = mxUtils.getXml(node);

    var output = "<diagram name=\"cmap\">";
    output += Graph.compress(modelString);
    output += "</diagram>";
    return output;
};

CmapDecoder.prototype.decode = function (xml)
{
    var graph = new Graph();
    var doc = mxUtils.parseXml(xml);
    var graphs = this.getDirectChildNamedElements(doc.documentElement, 'map');
    this.initialize(graphs[0]);
    var mxFile = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><mxfile>";
    var parent = graph.getDefaultParent(),cell
    for(let node of this.concepts){
        cell = graph.insertVertex(parent, node.id, node.label, node.x-node.width/2, node.y-node.height/2, node.width,node.height,node.style||graph.customizedNodeProps.style)
        cell.CustomizedType = graph.customizedNodeProps.type
        if(node.attachments&&node.attachments.length){
            cell.attachments = JSON.stringify(node.attachments)
        }
        if(node.links&&node.links.length){
            cell.links = JSON.stringify(node.links)
        }
        graph.showCustomizedIcons(cell)
        this.mxcells.push(cell);
    }
    for(let node of this.relations){
        cell = graph.insertVertex(parent, node.id, node.label, node.x-node.width/2, node.y-node.height/2, node.width,node.height,node.style||graph.customizedEdgeProps.style)
        cell.CustomizedType = graph.customizedEdgeProps.type
        this.mxcells.push(cell);
    }
    for(let edge of this.edges){
        let from = this.mxcells.find((cell)=>{
            return cell.id === edge.from
        })
        let to = this.mxcells.find((cell)=>{
            return cell.id === edge.to
        })
        cell = graph.insertEdge(parent, null, '', from, to, edge.style);
        if(edge.geoPoints){
            cell.geometry.points = edge.geoPoints
        }
        graph.orderCells(true,[cell])
    }
    mxFile += this.processPage(graph);
    mxFile += "</mxfile>";
    return mxFile
}

CmapDecoder.prototype.execute = function (xml, onLoad, onError)
{
    try
    {
        onLoad(this.decode(xml));
    }
    catch(e)
    {
        if (onError)
        {
            onError(e);
        }
    }
};

CmapDecoder.prototype.encode = function(data){
    let {concepts,rels,edges,resources} = data;
    let concepts_xml = concepts&&concepts.length?concepts.map((concept)=>{
        return `<concept id="${concept.id}" label="${encodeURIComponent(concept.value)}" style="${concept.style}"/>
                `
    }).join(''):''
    let concepts_appearance_xml = concepts&&concepts.length?concepts.map((concept)=>{
        return `<concept-appearance id="${concept.id}" x="${concept.x}" y="${concept.y}" width="${concept.width}" height="${concept.height}"/>
                `
    }).join(''):''
    let rels_xml = rels&&rels.length?rels.map((rel)=>{
        return `<linking-phrase id="${rel.id}" label="${encodeURIComponent(rel.value)}" style="${rel.style}"/>
                `
    }).join(''):''
    let rels_appearance_xml = rels&&rels.length?rels.map((rel)=>{
        return `<linking-phrase-appearance id="${rel.id}" x="${rel.x}" y="${rel.y}" width="${rel.width}" height="${rel.height}"/>
                `
    }).join(''):''
    let edges_xml = edges&&edges.length?edges.map((edge)=>{
        return `<connection id="${edge.id}" from-id="${edge.source}" to-id="${edge.target}" style="${(edge.style?edge.style:'')}" geo-points="${encodeURIComponent(edge.geoPoints?JSON.stringify(edge.geoPoints):'')}"/>
                `
    }).join(''):''
    let resouces_xml = resources && resources.length ? resources.map((resource) => {
        return `<resource-group parent-id="${resource.pid}" group-type="${resource.type}">
                    <resource id="${resource.id}" label="${resource.name}" resource-url="${resource.url}"/>
                </resource-group>
                `
    }).join('') : ''

    let tpl = `<?xml version="1.0" encoding="UTF-8"?>
<cmap xmlns:dcterms="http://purl.org/dc/terms/" xmlns="http://cmap.ihmc.us/xml/cmap/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:vcard="http://www.w3.org/2001/vcard-rdf/3.0#">
    <map>
        <concept-list>
            ${concepts_xml}
        </concept-list>
        <concept-appearance-list>
            ${concepts_appearance_xml}
        </concept-appearance-list>
        <linking-phrase-list>
            ${rels_xml}
        </linking-phrase-list>
        <linking-phrase-appearance-list>
            ${rels_appearance_xml}
        </linking-phrase-appearance-list>
        <connection-list>
            ${edges_xml}
        </connection-list>
        <resource-group-list>
            ${resouces_xml}
        </resource-group-list>
    </map>
</cmap>`
    return tpl;
}

EditorUi.prototype.doImportCmap = function(xmlData, onLoad, onerror)
{
    return new CmapDecoder(this).execute(xmlData, onLoad, onerror);
};
